#!/usr/bin/python
# bugzilla - a commandline frontend for the python bugzilla module
#
# Copyright (C) 2007 Red Hat Inc.
# Author: Will Woods <wwoods@redhat.com>
# 
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.

import bugzilla3, optparse
import os, sys, glob, re
import logging

version = '0.1'
default_bz = 'https://bugs.pld-linux.org/xmlrpc.cgi'

# Initial simple logging stuff
logging.basicConfig()
log = logging.getLogger("bugzilla")
if '--debug' in sys.argv:
    log.setLevel(logging.DEBUG)
elif '--verbose' in sys.argv:
    log.setLevel(logging.INFO)
        
def findcookie():
    globs = ['~/.mozilla/firefox/*default*/cookies.txt']
    for g in globs:
        log.debug("Looking for cookies.txt in %s", g)
        cookiefiles = glob.glob(os.path.expanduser(g))
        if cookiefiles:
            # return the first one we find.
            # TODO: find all cookiefiles, sort by age, use newest
            return cookiefiles[0]
cookiefile = None

def setup_parser():
    u =   "usage: %prog info|query|new|modify [options]"
    p = optparse.OptionParser(usage=u)
    # General bugzilla connection options
    # TODO: OptionGroup
    p.add_option('--bugzilla',default=default_bz,
            help="bugzilla XMLRPC URI. default: %s" % default_bz)
    p.add_option('--user',
            help="username. Will attempt to use browser cookie if not specified.")
    p.add_option('--password',
            help="password. Will attempt to use browser cookie if not specified.")
    p.add_option('--cookiefile',
            help="cookie file to use for bugzilla authentication")
    p.add_option('--verbose',action='store_true',
            help="give more info about what's going on")
    p.add_option('--debug',action='store_true',
            help="output bunches of debugging info")
    return p

def modify_parser(parser,action):
    p = parser
        # TODO: product and version could default to current system
        # info (read from /etc/redhat-release?)
    if action == 'new':
        p.add_option('-p','--product',
                help="REQUIRED: product name (list with 'bugzilla info -p')")
        p.add_option('-v','--version',
                help="REQUIRED: product version")
        p.add_option('-c','--component',
                help="REQUIRED: component name (list with 'bugzilla info -c PRODUCT')")
        p.add_option('-l','--comment',
                help="REQUIRED: initial bug comment")
        p.add_option('-s','--summary',dest='short_desc',
                help="REQUIRED: bug summary")
        p.add_option('-o','--os',default='Linux',dest='op_sys',
                help="OPTIONAL: operating system (default: Linux)")
        p.add_option('-a','--arch',default='All',dest='rep_platform',
                help="OPTIONAL: arch this bug occurs on (default: All)")
        p.add_option('--severity',default='medium',dest='bug_severity',
                help="OPTIONAL: bug severity (default: medium)")
        p.add_option('--priority',default='medium',dest='priority',
                help="OPTIONAL: bug priority (default: medium)")
        p.add_option('-u','--url',dest='bug_file_loc',default='http://',
                help="OPTIONAL: URL for further bug info")
        p.add_option('--cc',
                help="OPTIONAL: add emails to initial CC list")
        # TODO: alias, assigned_to, reporter, qa_contact, dependson, blocked
    elif action == 'query':
        p.add_option('-p','--product',
                help="product name (list with 'bugzilla info -p')")
        p.add_option('-v','--version',
                help="product version")
        p.add_option('-c','--component',
                help="component name (list with 'bugzilla info -c PRODUCT')")
        p.add_option('-l','--long_desc',
                help="search inside bug comments")
        p.add_option('-s','--short_desc',
                help="search bug summaries")
        p.add_option('-o','--cc',
                help="search cc lists for given address")
        p.add_option('-a','--assigned_to',
                help="search for bugs assigned to this address")
        p.add_option('--blocked',
                help="search for bugs that block this bug ID")
        p.add_option('--dependson',
                help="search for bugs that depend on this bug ID")
        p.add_option('-b','--bug_id',
                help="specify individual bugs by IDs, separated with commas")
        p.add_option('-t','--bug_status',default="NEW,VERIFIED,ASSIGNED,NEEDINFO,ON_DEV,FAILS_QA,REOPENED",
                help="comma-separated list of bug statuses to accept")
    elif action == 'info':
        p.add_option('-p','--products',action='store_true',
                help='Get a list of products')
        p.add_option('-c','--components',metavar="PRODUCT",
                help='List the components in the given product')
        p.add_option('-m','--milestones',metavar="PRODUCT",
                help='List the milestones for the given product')
        p.add_option('-o','--component_owners',metavar="PRODUCT",
                help='List components (and their owners)')
        p.add_option('-v','--versions',metavar="PRODUCT",
                help='List the versions for the given product')

    if action in ('new','query'):
        # output modifiers
        p.add_option('-f','--full',action='store_const',dest='output',
                const='full',default='normal',help="output detailed bug info")
        p.add_option('-i','--ids',action='store_const',dest='output',
                const='ids',help="output only bug IDs")
        p.add_option('--outputformat',
                help="Print output in the form given. You can use RPM-style "+
                "tags that match bug fields, e.g.: '%{bug_id}: %{short_desc}'")
    return p

if __name__ == '__main__':

    # Set up parser
    parser = setup_parser()
    # Get our action
    if len(sys.argv) > 1 and sys.argv[1] in ('info','query','new','modify'):
        action = sys.argv[1]
    else:
        parser.error("you must specify an action")
    # Add the action-specific args and such
    parser = modify_parser(parser,action)
    # Parse the commandline, woo
    (opt,args) = parser.parse_args()
    
    # Connect to bugzilla
    log.info('Connecting to %s',opt.bugzilla)
    bz = bugzilla3.Bugzilla(url = opt.bugzilla, user = opt.user, password = opt.password, cookie_file = opt.cookiefile)
    if opt.user and opt.password:
        log.info('Using username/password for authentication')
        bz.login()
    
    # And now we actually execute the given command
    buglist = list() # save the results of query/new/modify here
    if action == 'info':
        if opt.products:
            print 'Available products:'
            for k in sorted(bz.products):
                print '\t%d\t%s' % (k.id, k.name)

        if opt.components:
            p = bz.get_products(int(opt.components))
            print 'Components for product %s:' % p.name
            for c in sorted(p.components):
                print '\t%s' % c

        if opt.component_owners:
            component_details = bz.getcomponentsdetails(opt.component_owners)
            for c in sorted(component_details):
                print "%s: %s" % (c, component_details[c]['initialowner'])

        if opt.versions:
            p = bz.get_products(int(opt.versions))
            print 'Versions for product %s:' % p.name
            for c in sorted(p.versions):
                print '\t%s' % c

        if opt.milestones:
            p = bz.get_products(int(opt.milestones))
            print 'Milestones for product %s:' % p.name
            for c in sorted(p.milestones):
                print '\t%s' % c

    elif action == 'query':
        # shortcut for specific bug_ids
        if opt.bug_id:
            buglist = bz.get_bugs(opt.bug_id.split(','))
        else:
            # Construct the query from the list of queryable options
            q = dict()
            email_count = 1
            chart_id = 0
            for a in ('product','component','version','long_desc','bug_id',
                      'short_desc','cc','assigned_to','bug_status',
                      'blocked','dependson'):
                if hasattr(opt,a):
                    i = getattr(opt,a)
                    if i:
                        if a in ('bug_status'): # list args
                            q[a] = i.split(',')
                        elif a in ('cc','assigned_to'):
                            # the email query fields are kind of weird - thanks
                            # to Florian La Roche for figuring this bit out.
                            # ex.: {'email1':'foo@bar.com','emailcc1':True}
                            q['email%i' % email_count] = i
                            q['email%s%i' % (a,email_count)] = True
                            email_count += 1
                        elif a in ('blocked','dependson'): 
                            # Chart args are weird.
                            q['field%i-0-0' % chart_id] = a
                            q['type%i-0-0' % chart_id]  = 'equals'
                            q['value%i-0-0' % chart_id] = i
                            chart_id += 1
                        else:
                            q[a] = i
            log.debug("bz.query: %s", q)
            buglist = bz.query(q)

    elif action == 'new':
        data = dict()
        required=['product','component','version','short_desc','comment',
             'rep_platform','bug_severity','op_sys','bug_file_loc','priority']
        optional=['cc']
        for a in required + optional:
            i = getattr(opt,a)
            if i:
                data[a] = i
        for k in required:
            if k not in data:
                parser.error('Missing required argument: %s' % k)
        b = bz.file_bug(**data)
        buglist = [b]
        
    else:
        print "Sorry - '%s' not implemented yet." % action

    # If we're doing new/query/modify, output our results
    if action in ('new','query'):
        if opt.outputformat:
            format_field_re = re.compile("%{[a-z0-9_]+}")
            def bug_field(matchobj):
                fieldname = matchobj.group()[2:-1]
                return str(getattr(b,fieldname))
            for b in buglist:
                print format_field_re.sub(bug_field,opt.outputformat)
        elif opt.output == 'ids':
            for b in buglist:
                print b.bug_id
        elif opt.output == 'full':
            fullbuglist = bz.getbugs([b.bug_id for b in buglist])
            for b in fullbuglist:
                print b
                if b.cc: print "CC: %s" % " ".join(b.cc)
                if b.blocked: print "Blocked: %s" % " ".join([str(i) for i in b.blocked])
                if b.dependson: print "Depends: %s" % " ".join([str(i) for i in b.dependson])
                for c in b.longdescs:
                    print "* %s - %s (%s):\n%s\n" % (c['time'],c['name'],c['email'] or c['safe_email'],c['body'])
        elif opt.output == 'normal':
            for b in buglist:
                print '\t#%d\t%s%s' % (b.id, b.summary, ' (%s)' % b.internals['resolution'] if b.internals['resolution'] else '')
        else:
            parser.error("opt.output was set to something weird.")
